תרגול מספר 9: בנאים, שיטות של אובייקטים והכמסה נושאי התרגול: this Shallow Copy, Deep Copy )Encapsulation( הכמסה )visibility modifiers( הגבלת גישה Setters ו- Getters )Exceptions( חריגות בתרגול הקודם הכרנו את יסודות התכנות המונחה עצמים. המחלקה הינה תבנית ממנה ניתן לייצר אובייקטים. לכל אובייקט ישנו מצב )state( וישנה התנהגות.)behavior( המצב מתואר בעזרת שדות.)methods( וההתנהגות מתוארת בעזרת שיטות,)fields, data members( public class MyString { //class fields public char[] elements; public int length; לדוגמה, המחלקה MyString המייצגת רצף של תווים )מחרוזת( ומוגדרת כך: //empty constructor public MyString(){ elements = new char[0]; length = 0; //constructor with a parameter public MyString(String s){ length = s.length(); elements = new char[length]; for (int i = 0; i < s.length(); i++) { elements[i] = s.charat(i); //constructor with a parameter public MyString(char[] otherelements){ length = otherelements.length; elements = new char[length]; for (int i = 0; i < otherelements.length; i++) { elements[i] = otherelements[i]; //copy constructor public MyString(MyString other){ length = other.length; elements = new char[length]; for (int i = 0; i < other.length; i++) { elements[i] = other.elements[i]; 1
//class method public int length(){ return length; שימו לב: כאשר משתמשים בבנאי העתקה יש לשים לב שיתכן שהערך המועבר יהיה.null לדוגמה, אם נבצע את הקריאות הבאות: במקרה זה הערך המועבר לבנאי העתקה יהיה null וכשננסה לבצע את השורה )בבנאי ההעתקה(: שגיאת זמן ריצה מסוג.NullPointerException נקבל public class Point { public double x; public double y; public Point() { x = 0; y = 0; :Deep Copy ו- Shallow Copy בהרצאה ראינו את המחלקות Point ו-,Circle כאשר המחלקה Point הוגדרה כך: public Point(Point p) { x = p.x; y = p.y; public class Circle{ public Point center; public double radius; והמחלקה Circle הוגדרה כך: //constructors public Circle() { center = new Point(); radius = 0; public Circle(Point cen, double rad) { center = new Point(cen); if (rad >= 0) radius = rad; else raduis = 0; //Circle 2
ניתן להוסיף למחלקה Circle את ה- copy constructor הבא: ואז כאשר נבצע את הפעולות הבאות: אז גם ה- center של circ1 ישתנה. public Circle(Circle other) { center = other.center; radius = other.radius; Circle circ1 = new Circle(); Circle circ2 = new Circle(circ1); circ2.center.x = 4; גישה זו ידועה בשם Shallow Copy )העתקה שטחית( כיוון שאנו מעתיקים את הכתובת של ה- Circle המתקבל בפרמטר.other ניתן היה להשתמש בגישה של Deep Copy )העתקה עמוקה(, ולהעתיק רק את הערכים הפרימיטיביים הפנימיים. לדוגמה: public Circle(Circle other) { center = new Point(other.center); radius = other.radius; שימו לב שפה השתמשנו ב- copy constructor של Point על מנת להעתיק את.center לעיתים נרצה להשתמש בשיטת ה- Deep Copy ולעיתים בשיטת ה-,Shallow Copy תלוי בדרישות. :this ראינו כי ניתן להגדיר מספר בנאים למחלקה בעזרת.Overloading בהגדרת הבנאי, ניתן לבצע קריאה לבנאי אחר, בתנאי שזו השורה הראשונה בקוד. הדבר נעשה בעזרת המילה השמורה.this נוכל לשכתב את הבנאי המעתיק באופן הבא: //copy constructor public MyString(MyString other){ this(other.elements); 3
public MyString(){ this(""); ניתן גם לשנות את הבנאי הריק: ניתן להשתמש באופרטור this גם על מנת להבדיל בין פרמטרים אשר מועברים לשיטה לבין השדות של האובייקט. לדוגמה: public class MyString{ public char[] elements; public int length; public MyString(char[] elements, int length) { this.length = length; this.elements = new char[this.length]; for (int i = 0; i < this.length; i=i+1) this.elements[i] = elements[i]; הכמסה )encapsulation( והגבלת גישות modifiers( :)visibility עקרון ההכמסה מאפשר לאובייקט להכיל את המצב שלו ואת השיטות הקשורות לאובייקט. הרעיון מאחורי הכמסה הוא שכך אנו בעצם מייצגים מהן היכולות של האובייקט )ע"פ השיטות שהן )public ולא כיצד האובייקט עובד מבפנים. בדרך זו אנו בעצם מפשטים את האובייקט למשתמש )המשתמש לא צריך לדעת כיצד האובייקט עובד, אלא רק מה הוא עושה(. בעזרת עיקרון זה אנו משיגים: שמירה על חוקיות המצב של האובייקט מי שכותב את המחלקה יודע אילו ערכים חוקיים ואילו ערכים אינם חוקיים, ויכול להכריח אותם פשטות מי שכותב את המחלקה קובע מה המשתמש צריך לדעת על האובייקט )מבלי לתת את הפרטים הפנימיים של מימוש המחלקה( הגנה על המידע הפנימי באובייקט כך נוכל להבטיח שאף אחד לא יוכל לקרוא את הערכים הפנימיים של האובייקט :getters & setters 4
ע) מבוא למדעי המחשב - סמסטר א' תשע"א,תרגול מס' 9 על מנת להבטיח את עקרון ההכמסה, ניתן להגדיר שיטות אשר מאפשרות לנו להחזיר או לשנות את השדות של האובייקט. שיטות אלו נקראות getters ו-.setters שיטה שהיא getter היא שיטה אשר מחזירה ערך של שדה מסוים, ואילו שיטה שהיא setter היא שיטה אשר קובעת ערך של שדה מסוים. אם נקח לדוגמה את המחלקה Car המייצגת מכונית: public final int MAX_SPEED = 210; public final int MIN_SPEED = -20; public int speed; public int getspeed() { return speed; public void setspeed(int speed) { if ((speed >= MIN_SPEED) && (speed <= MAX_SPEED)){ this.speed = speed; ניתן לראות כי יש במחלקה getter בשם getspeed() המחזיר את מהירות המכונית, ו- setter בשם speed) setspeed(int המקבל מהירות חדשה, בודק האם המהירות חוקית )האם היא בטווח(, ואם כן קובע אותה כמהירות החדשה של המכונית. נשים לב שכיוון שהשדה speed מוגדר כ-,public עדיין ניתן לשנות את ערכו מחוץ לאובייקט, ולכן עקרון ההכמסה לא בא לידי ביטוי בדוגמה הזו. ב- Java ניתן להגביל גישה לשדה או לשיטה בעזרת שינוי ה- visibility modifier שלהן. לכן, על מנת לממש את עקרון ההכמסה נשתמש בשיטת הסתרת המידע hiding(,)data כלומר נסתיר שדות ושיטות פנימיות של אובייקט מפני גישה חיצונית )הקומפיילר יבטיח לנו שאף אחד לא יגש אליהם(. עד היום ראינו את ה- visibility modifier הפומבי:.public כאשר הצהרנו על משהו שהוא,public בעצם אפשרנו גישה לכולם )גם מחוץ לאובייקט ניתן היה לגשת לשדה או לשיטה(. נוכל להגביל את הגישה בעזרת המילה השמורה private "י החלפת המקומות בהם הגדרנו שדה או שיטה ב- public ל-.)private כל שדה או שיטה המוגדרים כ- private, אינם נגישים מחוץ לתחום הגדרת המחלקה. נוכל להגדיר את השדה speed בדוגמה הקודמת כ-,private וכך נבטיח שמחוץ למחלקה לא יוכלו לשנות אותו: private final int MAX_SPEED = 210; private final int MIN_SPEED = -20; private int speed; 5
Car car1 = new Car(); car1.speed = 20; // will not compile ואז כאשר היינו מנסים לקמפל את השורות הבאות: הייתה מתקבלת שגיאת קומפילציה, כיוון שהשדה מוגדר כ-.private לא בכל המקרים נרצה להוסיף לכל שדה getter ו setter אוטומטית. אם נגדיר שלכל אובייקט Car יקבע צבעו בעת היצירה ולא יהיה ניתן לשנות זאת עוד אך נוכל להחזירו, private String color; public Car(String color) { this.color = color; נוכל לבצע זאת באופן הבא: public String getcolor() { return color; אם למכונית ישנו קוד הפעלה, ולא נרצה שכל אובייקט יוכל לקרוא את ערך קוד ההפעלה, אז לא נגדיר private String code; public Car(String initialcode) { code = initialcode; public void startcar(string code) { if (!code.equals(this.code)) System.out.println("Wrong code"); else{ //start the car getter לשדה זה. לדוגמה: public void setnewcode(string oldcode, String newcode) { if (!oldcode.equals(code)) System.out.println("Wrong code"); else code = newcode; Exceptions חריגה היא אירוע המתרחש במהלך תוכנית המפר את תהליך הריצה הנורמאלי של פקודות התוכנית. 6
לעיתים חריגות מתרחשות בגלל תקלות בלתי צפויות, כגון בעיה בקובץ אליו כותבים )למשל אין הרשאות כתיבה(, ולעיתים בגלל תקלות תוכנה, כגון שליחת פרמטר לא מתאים לפונקציה. בעצם כבר נתקלנו בחריגות: :ArithmeticException ניסיון חלוקה באפס :IndexOutOfBoundsException חריגה ממערך :NullPointerException ניסיון לפעול על מערך בעל ערך null איך מתייחסים לחריגה: ישנן שלוש דרכים לעשות זאת: להתעלם אפשרי רק עבור RuntimeException לתפוס את ה- Exception על ידי שימוש במילים השמורות try ו- catch להעביר את ה- Exception הלאה על ידי שימוש במילה השמורה throws הפונקציה שאנו כותבים בכותרת דוגמא :1 RuntimeException public final int MAX_SPEED = 210; public final int MIN_SPEED = -20; private int speed; public void setspeed(int speed) throws RuntimeException { if ((speed >= MIN_SPEED) & (speed <= MAX_SPEED)) this.speed = speed; else throw new RuntimeException("illegel speed"); public static void main(string[] args) { Car car = new Car(); car.setspeed(100); public final int MAX_SPEED = 210; public final int MIN_SPEED = -20; private int speed; דוגמא :2 Exception 7
public void setspeed(int speed) throws Exception { if ((speed >= MIN_SPEED) & (speed <= MAX_SPEED)) this.speed = speed; else throw new Exception("illegel speed"); public static void main(string[] args) { Car car = new Car(); try{ car.setspeed(100); catch(exception e){ System.err.println("Caught Exception: " + e.getmessage()); 8